Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework handling general entity references (&entity;) #766

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

Mingun
Copy link
Collaborator

@Mingun Mingun commented Jun 21, 2024

This is a big change in handling general entity references and character references. Open PR early to get feedback.

With this changes we can correctly parse document

<!DOCTYPE root [
  <!ENTITY root "<root/>">
]>
&root;

as equivalent normalized document

<root/>

The updated custom_entities example shows how it would be possible to implement requirement from the specification about parsed general entities. Serde deserializer did not updated yet, because this is not trivial part and probably that will be done in another PR.

Of course, such change probably makes the performance worse, I didn't measure impact yet.

Closes #667

@codecov-commenter
Copy link

codecov-commenter commented Jun 30, 2024

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

Attention: Patch coverage is 61.05033% with 178 lines in your changes missing coverage. Please review.

Project coverage is 60.18%. Comparing base (a9391f3) to head (0631d47).
Report is 15 commits behind head on master.

Files with missing lines Patch % Lines
examples/custom_entities.rs 0.00% 117 Missing ⚠️
src/events/mod.rs 38.98% 36 Missing ⚠️
src/reader/buffered_reader.rs 83.09% 12 Missing ⚠️
benches/macrobenches.rs 0.00% 4 Missing ⚠️
src/de/mod.rs 88.00% 3 Missing ⚠️
src/errors.rs 0.00% 3 Missing ⚠️
benches/microbenches.rs 0.00% 1 Missing ⚠️
src/reader/slice_reader.rs 97.56% 1 Missing ⚠️
src/writer/async_tokio.rs 0.00% 1 Missing ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #766      +/-   ##
==========================================
- Coverage   60.21%   60.18%   -0.03%     
==========================================
  Files          41       41              
  Lines       16021    16409     +388     
==========================================
+ Hits         9647     9876     +229     
- Misses       6374     6533     +159     
Flag Coverage Δ
unittests 60.18% <61.05%> (-0.03%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@Mingun
Copy link
Collaborator Author

Mingun commented Jun 30, 2024

I finished work on the base part of the entities support. In this PR new Event::GeneralRef is added together with new BytesRef struct which represents the any &...; reference, including entity references and character references. Character references can be resolved by call BytesRef::resolve_char_ref(), entity references can be resolved by mapping from content of BytesRef to replacement text. Both usages are shown in the updated custom_entities example.

@dralley
Copy link
Collaborator

dralley commented Jun 30, 2024

I won't have a chance to fully review this for a couple of days. Quick question though, am I correct in thinking that this PR will mean that any time a text block contains one or more entity references, instead of the developer receiving one Event::Text containing everything between the opening and closing tags, they will receive a series of Event::Text and Event::GeneralRef which they will then need to merge back together themselves into the original text?

@Mingun
Copy link
Collaborator Author

Mingun commented Jun 30, 2024

Yes, you are correct. But that does not mean that he/she will needed to construct the complete text themselves. In the next PR I plan to rename Reader to the RawReader and add make it return borrow-only RawEvents, and in in the another PR introduce new Reader which will automatically merge all consequent Text, CData and GeneralRef events. This should be much more convenient for the average user. RawReader will only be needed for very fine control.

Because renames affects very many places, I want to do that in a separate PR to reduce noise in PR with new Reader.

Borrow-only reader-only events will be useful also in that sense that I plan to add offset member to them to track event position in the stream. When you construct event for writing you are obviously does not have position and I think it is better to not have a dummy value for it, in order to you couldn't mistakenly use writer event in the reading context and get the wrong position.

Because new Reader will have a stack of the RawReaders (in the same way as demonstrated in custom_entities example), it will be simple recreate readers when we will need to change encoding, so I think, that #158 is very close to resolving.

Comment on lines 263 to 289
Ok((_, false)) => {
// We want to report error at `&`, but offset was increased,
// so return it back (-1 for `&`)
$self.state.last_error_offset = start - 1;
Err(Error::Syntax(SyntaxError::UnclosedReference))
}
Copy link
Collaborator Author

@Mingun Mingun Jul 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found a code in ruffle in ruffle-rs/ruffle#10471 from @Aaron1011 that will be break (custom_unescape function), because currently dangling & will always return SyntaxError::UnclosedReference. I think I should add a new configuration option for this here

@Mingun
Copy link
Collaborator Author

Mingun commented Jul 21, 2024

@dralley, what do you think about this?

@@ -24,6 +24,9 @@ XML specification. See the updated `custom_entities` example!

- [#766]: Allow to parse resolved entities as XML fragments and stream events from them.
- [#766]: Added new event `Event::GeneralRef` with content of [general entity].
- [#766]: Added new configuration option `allow_dangling_amp` which allows to have
a `&` not followed by `;` in the textual data which is required for some applications
for compatibility reasons.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which applications?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant case from #719 here

@dralley
Copy link
Collaborator

dralley commented Oct 12, 2024

Sorry for not reviewing this promptly. Once the current set of PRs are merged, can you rebase?

@Mingun
Copy link
Collaborator Author

Mingun commented Oct 19, 2024

@dralley, you can review this. Initially I thought not to merge it until I implement other changes (in the follow-up PRs) that reworks the parser to reduce amount of releases with breaking changes, but because we already have enough amount of breaking changes, I think, it can be merged and included in next release.

@Mingun Mingun force-pushed the entity-ref branch 2 times, most recently from 00506c0 to c8fefad Compare October 20, 2024 20:01
<!ENTITY msg "hello world" >
]>
<test label="&msg;">&msg;</test>
struct MyReader<'i> {
Copy link
Collaborator

@dralley dralley Oct 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mentioned a future PR that would implement this functionality on Reader while leaving RawReader for a lower-level event stream. I presume that at that point this example would basically become trivial, as opposed to... this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point just being that the current example as-is is a bit much to expect people to implement or copy and paste, and I just want to check my understanding that it's not a long-term solution.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not really decided yet whether to leave this example as it is to demonstrate how the reader stack can be implemented if for some reason the standard solution does not work, or rewrite it to a new API. If it remains as it is, then there will be a mention of the standard way

@@ -54,7 +54,7 @@ fn parse_document_from_str(doc: &str) -> XmlResult<()> {
}
}
Event::Text(e) => {
criterion::black_box(e.unescape()?);
criterion::black_box(e.decode()?);
Copy link
Collaborator

@dralley dralley Oct 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About Reader returning merged events in the future - does that mean that the .decode() would no longer serve a functional purpose if there was no "decoding" (in the text encoding, utf-8 etc. sense) to do? (because the entities are expanded already).

And would there be any way to, say, return the original raw unexpanded XML between two tags from that wrapper, or would that be impossible without dropping to the RawReader level?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think, decode method will gone

And would there be any way to, say, return the original raw unexpanded XML between two tags from that wrapper, or would that be impossible without dropping to the RawReader level?

I think, it could be possible to implement that, but only if something requested that. I think, that this is niche feature. The API could be a special method that need to call instead of read_event() to get an unparsed content.

Mingun and others added 6 commits October 28, 2024 03:06
…onstruction in a text

failures (16):
  serde-de (9):
    borrow::escaped::element
    borrow::escaped::top_level
    resolve::resolve_custom_entity
    trivial::text::byte_buf
    trivial::text::bytes
    trivial::text::string::field
    trivial::text::string::naked
    trivial::text::string::text
    xml_schema_lists::element::text::string
  serde-migrated (1):
    test_parse_string
  serde-se (5):
    with_root::char_amp
    with_root::char_gt
    with_root::char_lt
    with_root::str_escaped
    with_root::tuple
  --doc (1):
    src\de\resolver.rs - de::resolver::EntityResolver (line 13)
Text events produces by the Reader can not contain escaped data anymore,
all such data is represented by the Event::GeneralRef
Fixed (18):
  serde-de (9):
    borrow::escaped::element
    borrow::escaped::top_level
    resolve::resolve_custom_entity
    trivial::text::byte_buf
    trivial::text::bytes
    trivial::text::string::field
    trivial::text::string::naked
    trivial::text::string::text
    xml_schema_lists::element::text::string
  serde-migrated (1):
    test_parse_string
  serde-se (5):
    with_root::char_amp
    with_root::char_gt
    with_root::char_lt
    with_root::str_escaped
    with_root::tuple
  --doc (3):
    src\de\resolver.rs - de::resolver::EntityResolver (line 13)
Copy link
Collaborator

@dralley dralley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks reasonable.

Initially I thought not to merge it until I implement other changes (in the follow-up PRs) that reworks the parser to reduce amount of releases with breaking changes, but because we already have enough amount of breaking changes, I think, it can be merged and included in next release.

I personally would prefer to see the Reader / RawReader split happen prior to the next release, or even in this PR in subsequent commits, to reduce the amount of massively breaking API changes going on. I'd rather not split the changes across many releases.

From a purely psychological level I'd be deeply annoyed by having to change code that uses Reader to handle the existence of references throughout the returned event stream, and then subsequently needing to change it back later to something closer to the original approach. That particular type of churn (needing to make a change as a user and then un-make it later) is best avoided.

@Mingun
Copy link
Collaborator Author

Mingun commented Nov 10, 2024

Totally agree. Ok, then I'll merge it when follow-up PR will be ready

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

How would I parse character references as literal bytes and not codepoints?
3 participants